#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include "drv_pcie_dma.h"

/* 初始化 PCIe DMA */
int init_pcie_dma(void *parent, struct device *dev, 
                  struct pcie_dma_t *dma, void __iomem *reg_base)
{
    /* 检查参数是否有效 */
    if (!parent || !dev || !dma) {
        pr_err("Invalid parameters for %s, %p, %p, %p", 
               __func__, dma, dma->parent, dma->dev);
        return -ENODEV;
    }

    dma->parent = parent;
    dma->dev = dev;
    dma->reg_base = reg_base;
    /* 分配DMA内存（ coherent 内存：物理连续，且内核/设备可访问） */
    dma->alloc_virt = dma_alloc_coherent(dma->dev, DMA_DEFAULT_SIZE, &dma->alloc_phys, GFP_KERNEL);
    if (!dma->alloc_virt) {
        dev_err(dma->dev, "Allocate DMA memory failed!");
        return -ENOMEM;
    }
    dma->pl_size = DMA_DEFAULT_SIZE;
    dma->ps_addr = dma->alloc_phys;
    dma->pkt_addr = 0;
    dma->pkt_len = DMA_PACKET_LEN_MAX;
    dma->pkt_size = DMA_PACKET_LEN_MAX;
    dma->pkt_cnt = 0;
    dma->frame_size = DMA_FRAME_SIZE;
    dma->frame_cnt = 0;
    dma->frame_head = 0;
    dma->frame_tail = 0;
    dma->frame_done = false;

    return 0;
}

/* 开始 PCIe DMA 传输 */
int start_pcie_dma(struct pcie_dma_t *dma)
{
    int ret = 0;
    u32 val = 0;
    u32 i = 0;

    /* 检查参数是否有效 */
    if (!dma || !dma->parent || !dma->dev) {
        pr_err("Invalid parameters for %s, %p, %p, %p", 
               __func__, dma, dma->parent, dma->dev);
        return -ENODEV;
    }

    /* 设置内存低32位地址（BAR1+0x200）*/
    val = (u32)(dma->ps_addr & 0xFFFFFFFF);
    iowrite32(val, dma->reg_base + DMA_CPU_PHYS_ADDR_LOW_REG);
    // dev_dbg(dma->dev, "Set addr low: 0x%x", val);
    /* 设置内存高32位地址（BAR1+0x204）*/
    val = (u32)((dma->ps_addr >> 32) & 0xFFFFFFFF);
    iowrite32(val, dma->reg_base + DMA_CPU_PHYS_ADDR_HIGH_REG);
    // dev_dbg(dma->dev, "Set addr high: 0x%x", val);
    /* 设置CplD初始地址（BAR1+0x210）*/
    val = (u32)(dma->pkt_addr);
    iowrite32(val, dma->reg_base + DMA_FPGA_RAM_ADDR_REG);
    // dev_dbg(dma->dev, "Set CplD addr: 0x%x", val);
    /* 设置数据包长度（BAR1+0x220）*/
    val = (u32)(((dma->pkt_len) >> 2) - 1);    /* 单位 DW*/
    iowrite32(val, dma->reg_base + DMA_FPGA_RAM_LEN_REG);
    // dev_dbg(dma->dev, "Set packet len: %u bytes", val);
    /* 设置数据包类型（BAR1+0x240）*/
    dma->addr_type = ((dma->alloc_phys >> 32) > 0) ? 1 : 0;
    iowrite32(dma->addr_type, dma->reg_base + DMA_FPGA_DATA_TYPE_REG);
    // dev_dbg(dma->dev, "Set address type: %d", dma->addr_type);
    /* 检查DMA状态（BAR1+0x230，1:完成，0:未完成）*/
    for (i = 0; i < DMA_WAIT_DATA_CPL_CNT; i++) {
        val = ioread32(dma->reg_base + DMA_FPGA_STATUS_REG);
        if (1 == val) {
            // dev_dbg(dma->dev, "DMA completed successfully (status=%d cnt=%d).", val, i);
            break;
        }
    }
    if (DMA_WAIT_DATA_CPL_CNT == i) {
        dev_info(dma->dev, "DMA failed (status: %u)", val);
        ret--;
    }

    return ret;
}

/* 更新 PCIe DMA 地址，返回值 1：继续DMA开始传输；0：无须继续DMA传输*/
int update_pcie_dma_addr(struct pcie_dma_t *dma)
{
    int ret = 0;

    /* 检查参数是否有效 */
    if (!dma || !dma->parent || !dma->dev) {
        pr_err("Invalid parameters for %s, %p, %p, %p", 
               __func__, dma, dma->parent, dma->dev);
        return -ENODEV;
    }

    /* 当前版本要求pkt_len、pkt_size、frame_size固定不变，且可以被pl_size整除！*/
    dma->pkt_cnt = ((dma->pkt_cnt + 1) % (dma->pl_size / dma->pkt_len));
    /* 0~256KB每4KB间隔递增 */
    dma->pkt_addr = (u32)((dma->pkt_cnt % (dma->pl_size / dma->pkt_len)) * dma->pkt_len);
    /* ps_addr初始值为alloc_phys，逐包偏移地址，当大于等于pl_size(256KB)时，恢复初始位置 */
    dma->ps_addr += dma->pkt_len;
    if ((dma->ps_addr + dma->pkt_len) > (dma->alloc_phys + dma->pl_size)) {
        dma->ps_addr = dma->alloc_phys;
    }

    return ret;
}
